package net.xdclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.CouponCategoryEnum;
import net.xdclass.enums.CouponPublishEnum;
import net.xdclass.enums.CouponStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.CouponMapper;
import net.xdclass.mapper.CouponRecordMapper;
import net.xdclass.model.CouponDO;
import net.xdclass.model.CouponRecordDO;
import net.xdclass.model.LoginUser;
import net.xdclass.request.NewUserCouponRequest;
import net.xdclass.service.CouponService;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.JsonData;
import net.xdclass.vo.CouponVo;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author : zhen.xue
 * @date : 2021-05-03 22:21
 */
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {


    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private RedissonClient redissonClient;


    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {
        Page<CouponDO> pageInfo = new Page<>(page,size);
        Page<CouponDO> couponDOPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
                .eq("publish", CouponPublishEnum.PUBLISH)
                .eq("category", CouponCategoryEnum.PROMOTION)
                .orderByDesc("create_time"));
        HashMap<String, Object> pageMap = new HashMap<>(3);
        pageMap.put("total_record",couponDOPage.getTotal());
        pageMap.put("total_page",couponDOPage.getPages());
        pageMap.put("current_date",couponDOPage.getRecords().stream().map(obj->{
            CouponVo couponVo = new CouponVo();
            BeanUtils.copyProperties(obj,couponVo);
            return couponVo;
        }).collect(Collectors.toList()));
        return pageMap;
    }

    @Transactional(rollbackFor=Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public JsonData addCoupon(long couponId, CouponCategoryEnum category) {
        //    领取优惠卷
        //  1 获取优惠卷是否存在
        //  2 校验优惠券是否可以领取:时间 库存 超时 限制
        //  3 扣减库存
        //  4 保存领卷记录
        LoginUser user = LoginInterceptor.threadLocal.get();

        String loackKey = "lock:coupon:"+couponId;
        RLock lock = redissonClient.getLock(loackKey);
        // 多线程阻塞
        lock.lock();
        log.info("领券接口加锁成功:{}",Thread.currentThread().getName());
//        自动续期过期时间无法使用断点查看
//        try {
//            TimeUnit.SECONDS.sleep(90);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        try{
            // 执行业务逻辑
            CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
                    .eq("id", couponId)
                    .eq("cateGory", category.name()));
            if(couponDO == null){
                throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
            }
            // 优惠券是够可以领取
            this.checkCoupon(couponDO,user.getId());
            // 构建领券记录
            CouponRecordDO couponRecordDO = new CouponRecordDO();
            BeanUtils.copyProperties(couponDO,couponRecordDO);
            couponRecordDO.setCreateTime(new Date());
            couponRecordDO.setUseState(CouponStateEnum.NEW.name());
            couponRecordDO.setUserId(user.getId());
            couponRecordDO.setCouponId(couponId);
            couponRecordDO.setUserName(user.getName());
            couponRecordDO.setId(null);
            // 扣减库存
            int rows =  couponMapper.reduceStock(couponId);
            if(rows == 1){
                // 库存扣减成功保存记录
                couponRecordMapper.insert(couponRecordDO);
            }else{
                log.warn("发放优惠券失败:{},用户:{}",couponDO,user);
                throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
            }
        }finally {
            lock.unlock();
            log.info("解锁成功");
        }
        return JsonData.buildSuccess();
    }


    @Transactional(rollbackFor=Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public JsonData initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        LoginUser user = LoginUser.builder().build();
        user.setId(newUserCouponRequest.getUserId());
        user.setName(newUserCouponRequest.getName());
        LoginInterceptor.threadLocal.set(user);
        // 查询新用户有哪些优惠券
        List<CouponDO> couponDOList = couponMapper.selectList(new QueryWrapper<CouponDO>().eq("category", CouponCategoryEnum.NEW_USER.name()));
        for (CouponDO couponDO : couponDOList) {
            // 幂等操作需要增加锁
            addCoupon(couponDO.getId(),CouponCategoryEnum.NEW_USER);
        }
        return JsonData.buildSuccess();
    }


    /**
     * 检查优惠卷是否可以领取
     * @param couponDO 优惠卷
     * @param id 用户id
     */
    private void checkCoupon(CouponDO couponDO, Long id) {
        // 库存是否充足
        if(couponDO.getStock()<=0){
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
        if(!CouponPublishEnum.PUBLISH.name().equals(couponDO.getPublish())){
            throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
        }
        // 是否在领取时间范围
        long time = CommonUtil.getCurrentTimeMillis();
        long startTime = couponDO.getStartTime().getTime();
        long endTime = couponDO.getEndTime().getTime();
        if(time<startTime || time >endTime){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }
        // 用户是否超过限制
        Integer recordCound = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>().eq("coupon_id", couponDO.getId()).eq("user_id", id));
        if(recordCound >= couponDO.getUserLimit()){
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }
}